traceMe = False


def trace(*args):
    if traceMe:
        print('[%s]' % ' '.join(map(str, args)))


def accessControl(failIf):
    def onDecorator(aClass):
        class onInstance(BuiltinsMixin):
            def __init__(self, *args, **kargs):
                self.__wrapped = aClass(*args, **kargs)

            def __getattr__(self, attr):
                trace('get:', attr)
                if failIf(attr):
                    raise TypeError('private attribute fetch:'+attr)
                else:
                    return getattr(self.__wrapped, attr)

            def __setattr__(self, attr, value):
                trace('set:', attr, value)
                if attr == '_onInstance__wrapped':
                    self.__dict__[attr] = value
                elif failIf(attr):
                    raise TypeError('private attribute change:'+attr)
                else:
                    setattr(self.__wrapped, attr, value)
        return onInstance
    return onDecorator


def Private(*attributes):
    return accessControl(failIf=lambda attr: attr in attributes)


def Public(*attributes):
    return accessControl(failIf=lambda attr: attr not in attributes)


class BuiltinsMixin:
    def __str__(self):
        return self.__class__.__getattr__(self, '__str__')()


if __name__ == '__main__':
    traceMe = True

    @Private('data', 'size')
    class Doubler:
        def __init__(self, label, start):
            self.label = label
            self.data = start

        def size(self):
            return len(self.data)

        def double(self):
            for i in range(self.size()):
                self.data[i] = self.data[i]*2

        def __str__(self):
            return 'str information'

        def display(self):
            print('%s => %s' % (self.label, self.data))

    X = Doubler('X is', [1, 2, 3])
    Y = Doubler('Y is', [-10, -20, -30])

    # The following all succeed
    print(X.label)  # Accesses outside subject class
    X.display()
    X.double()
    X.display()     # Intercepted: validated, delegated
    print(Y.label)
    Y.display()
    Y.double()
    Y.label = 'Spam'
    Y.display()
    # The following all fail properly

    # print(X.size()) # prints "TypeError: private attribute fetch: size"
    # print(X.data)
    # X.data = [1, 1, 1]
    # X.size = lambda S: 0
    # print(Y.data)
    # print(Y.size())
